Code of the statistical analysis for the article ‘Life Beyond A Jar: Effects of Tank Size and Enrichment on the Behaviour and Welfare of Siamese Fighting Fishes’

Author

Juliette Tariel-Adam

Published

April 15, 2024

The script contains the code of data transformation, analysis and graphs for the article “Life Beyond A Jar: Effects of Tank Size and Enrichment on the Behaviour and Welfare of Siamese Fighting Fishes” by XXauthorsXXX. It is in two versions: the raw R script in qmd format and the code output in html format.

The goal was to test the effect of tank enrichment and size on Siamese fish behaviour. The tanks Jar, Small, Medium and Large are compared among each other, and the tank Large and Barren between each other. Barren is not compared to Jar, Small or Medium because they differ by two factors: enrichment and size.

On the html version of the code, you fill find below a first row of tabs to click on (PCA, Swimming, etc) and for most of these tabs, a second row of tabs to click on (Distribution, Plot, etc).

After staying 3-7 days in a tank, the behaviour of all fish were scored by four 10-min trials in their tank. All trials occurred on the same day at 4 different times (7 am, 10 am, 2 pm, 6 pm). During the 10 min trial = 600 sec, the fish’s behaviour was assigned to one of the 10 behaviours, meaning that the behaviours are mutually exclusive (for instance, a fish can’t be scored as nest building and interacting with the surface at the same time): Resting, Swimming, Hovering, Sinking/Floating, Stereotypic swimming, Nest building, Foraging, Interation with surface, Out of view, Unsure. Unsure is not present in the raw dataset. Out.of.view is a bit special because it is not a behaviour and was not analysed. Sinking/Floating was not analysed (see explanation in the PCA section).

The data has been inspected and corrected for data entry errors (sum of scored behaviours too low/high, column shifted, typos, inconsistent nb of observations per fish/time/tank…).

Each row represents a trial. For each trial, there is: + Fish: the fish tested + Tank: in which tank the fish was + Order: the tank order, i.e. whether it was the first, second, …, fifth tank that the fish experienced. + Time: time of the day at which the trial happened + Filter: where there a filter in the tank + Behavioural columns. The columns for the behaviours are the time spent doing the specific behaviour in seconds during the trial. Some behaviours (Forgaging, Hovering, Interaction.with.Surface, Stereotypic.swimming) have been trasnformed in binary variable (.bin). The columns .bins represents if the fish performed the specific behaviour at all during the trial or not.

summary(data)
      Fish         Tank        Order             Time          Filter   
 Elf    : 20   Jar   :48   Min.   :1.000   7:00 AM :63   No-filter:180  
 Fairy  : 20   Small :52   1st Qu.:2.000   10:00 AM:63   Filter   : 72  
 Ghost  : 20   Medium:52   Median :3.000   2:00 PM :63                  
 Goblin : 20   Large :52   Mean   :2.968   6:00 PM :63                  
 Kinara : 20   Barren:48   3rd Qu.:4.000                                
 Kraken : 20               Max.   :5.000                                
 (Other):132                                                            
    Swimming        Resting          Hovering      Stereotypic.swimming
 Min.   :  0.0   Min.   :  0.00   Min.   :  0.00   Min.   :  0.00      
 1st Qu.:133.9   1st Qu.: 92.33   1st Qu.:  6.01   1st Qu.:  0.00      
 Median :224.8   Median :190.99   Median : 23.71   Median :  0.00      
 Mean   :224.6   Mean   :226.04   Mean   : 42.93   Mean   : 42.47      
 3rd Qu.:309.2   3rd Qu.:354.97   3rd Qu.: 56.21   3rd Qu.: 20.58      
 Max.   :486.8   Max.   :600.00   Max.   :344.27   Max.   :549.00      
                                                                       
 Nest.building       Foraging      Interation.with.surface Foraging.bin
 Min.   :  0.00   Min.   :  0.00   Min.   :  0.000         No :168     
 1st Qu.:  0.00   1st Qu.:  0.00   1st Qu.:  0.000         Yes: 84     
 Median :  0.00   Median :  0.00   Median :  1.545                     
 Mean   : 33.72   Mean   :  7.83   Mean   : 15.331                     
 3rd Qu.:  0.00   3rd Qu.:  7.80   3rd Qu.: 14.117                     
 Max.   :512.99   Max.   :125.28   Max.   :251.000                     
                                                                       
 SS.bin    Hovering.bin Interacting.bin Nest.bin 
 No :172   No : 48      No :125         No :201  
 Yes: 80   Yes:204      Yes:127         Yes: 51  
                                                 
                                                 
                                                 
                                                 
                                                 
# Number of observations per fish per tank
data %>% group_by(Fish, Tank) %>%
  summarise(n=n(), .groups = 'drop') %>%
  spread(Tank,n) 
# A tibble: 13 × 6
   Fish            Jar Small Medium Large Barren
   <fct>         <int> <int>  <int> <int>  <int>
 1 Dragon           NA     4      4     4      4
 2 Elf               4     4      4     4      4
 3 Fairy             4     4      4     4      4
 4 Ghost             4     4      4     4      4
 5 Goblin            4     4      4     4      4
 6 Kinara            4     4      4     4      4
 7 Kraken            4     4      4     4      4
 8 Merlion           4     4      4     4     NA
 9 Naga              4     4      4     4      4
10 Orange Pendek     4     4      4     4      4
11 Pegasus           4     4      4     4      4
12 Phoenix           4     4      4     4      4
13 Wizard            4     4      4     4      4
# Number of observations per fish per time
data %>% group_by(Fish, Time) %>%
  summarise(n=n(), .groups = 'drop') %>%
  spread(Time,n) 
# A tibble: 13 × 5
   Fish          `7:00 AM` `10:00 AM` `2:00 PM` `6:00 PM`
   <fct>             <int>      <int>     <int>     <int>
 1 Dragon                4          4         4         4
 2 Elf                   5          5         5         5
 3 Fairy                 5          5         5         5
 4 Ghost                 5          5         5         5
 5 Goblin                5          5         5         5
 6 Kinara                5          5         5         5
 7 Kraken                5          5         5         5
 8 Merlion               4          4         4         4
 9 Naga                  5          5         5         5
10 Orange Pendek         5          5         5         5
11 Pegasus               5          5         5         5
12 Phoenix               5          5         5         5
13 Wizard                5          5         5         5
# Sum of all behaviours per trial
data3$total <- rowSums(dplyr::select(data3, beh.cols3)) 
sort(data3$total) 
  [1] 556.750 557.010 559.290 571.820 575.700 583.180 584.820 586.990 588.230
 [10] 588.970 592.550 592.810 592.910 593.320 594.830 595.190 595.330 595.560
 [19] 595.760 595.990 596.190 596.270 596.290 596.500 596.600 596.890 596.910
 [28] 597.288 597.370 597.490 597.610 597.700 597.770 597.830 597.890 598.070
 [37] 598.350 598.470 598.480 598.500 598.640 598.730 598.750 599.060 599.560
 [46] 599.580 599.640 599.720 599.810 600.000 600.000 600.040 600.090 600.290
 [55] 600.400 600.410 600.440 600.610 600.620 600.750 600.780 600.880 600.930
 [64] 601.030 601.050 601.070 601.110 601.300 601.360 601.380 601.470 601.550
 [73] 601.560 601.570 601.630 601.650 601.650 601.670 601.680 601.710 601.750
 [82] 601.770 601.810 601.820 601.820 601.860 601.870 601.960 601.970 602.050
 [91] 602.050 602.060 602.060 602.090 602.090 602.110 602.130 602.160 602.160
[100] 602.170 602.210 602.220 602.370 602.430 602.430 602.450 602.470 602.620
[109] 602.620 602.640 602.660 602.670 602.710 602.740 602.760 602.760 602.790
[118] 602.790 602.870 602.900 602.920 602.960 602.970 602.990 603.000 603.010
[127] 603.100 603.120 603.120 603.170 603.190 603.220 603.220 603.250 603.290
[136] 603.290 603.310 603.360 603.370 603.380 603.390 603.400 603.410 603.450
[145] 603.490 603.520 603.650 603.670 603.690 603.690 603.700 603.720 603.730
[154] 603.800 603.820 603.840 603.880 603.880 603.890 603.910 603.940 604.010
[163] 604.010 604.020 604.040 604.110 604.120 604.130 604.130 604.190 604.200
[172] 604.210 604.230 604.260 604.300 604.300 604.310 604.320 604.350 604.360
[181] 604.360 604.380 604.380 604.400 604.410 604.410 604.430 604.470 604.520
[190] 604.550 604.590 604.610 604.640 604.660 604.660 604.670 604.670 604.680
[199] 604.690 604.710 604.720 604.730 604.750 604.780 604.790 604.810 604.870
[208] 604.890 604.900 604.930 604.930 604.950 604.960 605.000 605.000 605.010
[217] 605.030 605.060 605.280 605.290 605.310 605.330 605.470 605.470 605.570
[226] 605.650 605.780 605.830 605.910 605.980 606.000 606.150 606.360 607.020
[235] 607.170 607.680 607.700 608.260 608.540 608.810 609.200 609.340 609.450
[244] 610.030 610.130 612.490 612.530 614.340 614.850 616.970 622.480 624.680

The sum of all behaviours is not exactly 600 sec (as Unsure is not in the dataset).

The mean value over multiple trials is plotted to only have one value per tank and per behaviour.

# nb of fish had a filter in their tank
table(data$Tank, data$Filter)/4 
        
         No-filter Filter
  Jar           12      0
  Small         13      0
  Medium         7      6
  Large          7      6
  Barren         6      6
# nb of times each fish had a filter in their tank
table(data$Fish, data$Filter)/4 
               
                No-filter Filter
  Dragon                1      3
  Elf                   5      0
  Fairy                 2      3
  Ghost                 2      3
  Goblin                5      0
  Kinara                5      0
  Kraken                2      3
  Merlion               4      0
  Naga                  5      0
  Orange Pendek         5      0
  Pegasus               5      0
  Phoenix               2      3
  Wizard                2      3
plot1("Filter", main = data3, beh_cols = beh.cols3, palette = palette.beh3)

There have never been a filter in Jar or Small. Some fish never had a filter in their tank. Filter needs to be included in all models as a control variable.

We run a PCA in order to:

  • Try to reduce the number of variables to analyse
  • Determine which behaviours were important to explain the variability between trials
  • See correlations between behaviours

The PCA did not help reduce the number of variables to analyse (see Analysis_PCA.html for more explanation) so the behaviours were analysed separately with linear models. The advantage of separate linear models compared to the PCA is the possibility to quantitatively interpret the differences between tanks.

Even if we did not use PCA for further analysis, the first 4 components are plotted below, which explained 71% of variance in the data, to show the important variables and the correlations between variables.

plot(pca, choix = "var") + labs(title = NULL)

plot(pca, choix = "var", axes = c(3,4)) + labs(title = NULL)

# Contribution to each principal component
round(pca$var$cos2,2)
                        Dim.1 Dim.2 Dim.3 Dim.4
Swimming                 0.68  0.01  0.10  0.00
Resting                  0.80  0.00  0.14  0.04
Hovering                 0.00  0.51  0.04  0.11
Stereotypic.swimming     0.06  0.52  0.13  0.23
Nest.building            0.01  0.06  0.57  0.19
Foraging                 0.32  0.01  0.16  0.18
Interation.with.surface  0.09  0.16  0.05  0.21
Sinking.Floating         0.06  0.00  0.03  0.18
# Extent to which each behavioural type contributes to the total variance explained by the first 3 components
rowSums(pca$var$contrib[,1:3])/3
               Swimming                 Resting                Hovering 
              13.946992               17.017016               14.360858 
   Stereotypic.swimming           Nest.building                Foraging 
              18.372673               17.241361               10.134302 
Interation.with.surface        Sinking.Floating 
               7.115428                1.811372 
  • The 1st PCA component was driven by Resting (cos2 = 0.8), Swimming (0.7) and Foraging (0.3). Swimming and Foraging opposite to Resting. This first component could be interpret as “activity”. If a fish spent a lot of time swimming and foraging during a trial, it was very little resting. This first component explained 25% of the variance in the data (as a reminder, the data is the time spent performing the different types of behaviour). So 1 quarter of the variance is a matter of activity. Keep in mind for the rest of the analyses that these behavioural types are correlated.

  • The 2nd PCA component was driven by Hovering (cos2 = 0.5) and Stereotypic.swimming (0.5), and a bit by Interaction with surface (0.16). Hovering and Interacting.with.Surface opposite to Stereotypic.swimming. If the fish spent a lot of time hovering (and interacting with the surface), it was very little stereotypic swimming.

  • The 3rd PCA component was driven mainly by Nest building (cos2 = 0.6), and a bit by Foraging (0.16), Resting (0.14) and Stereotypic Swimming (0.13). Foraging and Resting opposite to Nest building and Stereotypic Swimming. If a fish spent time foraging and resting in a trial, it was little nest building and stereotypic swimming. This makes sense, it a fish is building a nest,it is an important task and needs to be finished, so it is not scattering its time with other activities. The apparent correlation between nest building and stereotypic swimming could be due to Jar where fish in this tank rarely engaged in nest building or stereotypic swimming.

  • The 4th PCA component was not easily interpretable = not easily linked to the initial behavioural variables.

Sinking/Floating contributed almost nothing to the PCA (1.8%) + did not correlate strongly with any of the component. Sinking/Floating was not important to explain the variability between trials. We did not further analyse this behaviour.

hist1(data$Swimming)

The first plot is to see the trend by tank. Big red dots is for the overall mean by tank. Small black points are raw data = swimming time of each trial.

The second plot is to see the trend by fish and by tank. The dashed black line is means by tank. The other lines are the means for each fish by tank.

plot_var(data, "Swimming")

plot_var_fish(data, "Swimming")

mSwimming <- lmer(Swimming ~ Tank + Time + Filter + (1|Fish), data = data)
anova(mSwimming, ddf = "Kenward-Roger", type = 2)
Type II Analysis of Variance Table with Kenward-Roger's method
       Sum Sq Mean Sq NumDF  DenDF F value    Pr(>F)    
Tank   240332   60083     4 236.97  10.065 1.535e-07 ***
Time   337219  112406     3 231.05  18.832 5.812e-11 ***
Filter 111221  111221     1 151.81  18.633 2.846e-05 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
calc_contrasts(mSwimming)
         contrast estimate   SE  df t.ratio p.value lower.CL upper.CL
1    jar_vs_small      -14 15.5 232   -0.90  >0.999      -56       28
2   jar_vs_medium      -12 17.4 242   -0.69  >0.999      -59       35
3    jar_vs_large      -93 17.4 242   -5.31  <0.001     -140      -45
4 small_vs_medium        2 17.4 243    0.11  >0.999      -45       49
5  small_vs_large      -79 17.4 243   -4.53  <0.001     -126      -32
6 medium_vs_large      -81 15.2 231   -5.32  <0.001     -122      -39
7 large_vs_barren       58 15.5 232    3.76  <0.001       16      101
emmeans(mSwimming, ~ Time) # estimated means of time spent swimming depending on the time of the day 
 Time     emmean   SE   df lower.CL upper.CL
 7:00 AM     301 14.9 28.9      270      331
 10:00 AM    243 14.9 28.9      212      273
 2:00 PM     206 14.9 28.9      176      237
 6:00 PM     216 14.9 28.9      186      247

Results are averaged over the levels of: Tank, Filter 
Degrees-of-freedom method: kenward-roger 
Confidence level used: 0.95 
emmeans(mSwimming, ~ Filter)
 Filter    emmean   SE   df lower.CL upper.CL
 No-filter    202 12.8 15.2      175      229
 Filter       281 17.6 34.4      246      317

Results are averaged over the levels of: Tank, Time 
Degrees-of-freedom method: kenward-roger 
Confidence level used: 0.95 

Effect of Tank, Time and Filter!

  • Tank. Large swam significantly more time than Jar, Small and Medium. Fish in Large swan 93 sec [CI 45, 140] more than Jar (over a 600 sec trial), 79 sec [CI 32, 126] more than Small, and 81 sec [CI 39, 122] more than Medium. In addition, fish in Large swam significantly more time (58 sec [CI 16, 101]) than fish in Barren.
  • Time. Fish swim more in the morning than in the afternoon, especially at 7 am.
  • Filter. Fish swim more with a filter in their tank.
# Uncomment the 2 lines below if running the script for the first time
## rpt.swimming <- rpt(Swimming ~ Tank + Time + Filter + (1|Fish), grname = "Fish", data = data, datatype = "Gaussian", nboot = 1000, npermut = 1000)
## save(rpt.swimming, file = "Script/Repeatability rptR output/rpt.swimming")
base::load("Script/Repeatability rptR output/rpt.swimming")
summary(rpt.swimming)

Repeatability estimation using the lmm method

Call = rpt(formula = Swimming ~ Tank + Time + Filter + (1 | Fish), grname = "Fish", data = data, datatype = "Gaussian", nboot = 1000, npermut = 1000)

Data: 252 observations
----------------------------------------

Fish (13 groups)

Repeatability estimation overview: 
      R     SE   2.5%  97.5% P_permut  LRT_P
  0.198 0.0802 0.0441  0.352    0.001      0

Bootstrapping and Permutation test: 
            N    Mean Median   2.5%  97.5%
boot     1000 0.19068  0.186 0.0441 0.3520
permut   1000 0.00981  0.000 0.0000 0.0504

Likelihood ratio test: 
logLik full model = -1459.844
logLik red. model = -1474.76
D  = 29.8, df = 1, P = 2.35e-08

----------------------------------------

Likelihood ratio test indicates a statistically significant random effect. Meaning that fish behaved differently among each others in terms of swimming. But the repeatability is quite low (0.2 [95% CI 0.04, 0.35]).

lm_diagnosis(mSwimming)
[1] "check constant variance over fitted values"

[1] "Normality of Swimming"

[1] "Normality random residuals"

The model seems good.

hist1(data$Resting)

table(data$Resting==0)

FALSE  TRUE 
  241    11 

Only 11 trials where the fish did not rest at all.

plot_var(data, "Resting")

plot_var_fish(data, "Resting")

mResting <- lmer(Resting ~ Tank + Time + Filter + (1|Fish), data = data)
anova(mResting, ddf = "Kenward-Roger", type = 2)
Type II Analysis of Variance Table with Kenward-Roger's method
       Sum Sq Mean Sq NumDF  DenDF F value    Pr(>F)    
Tank   547562  136891     4 235.30 10.3044 1.049e-07 ***
Time   795993  265331     3 231.01 19.9736 1.514e-11 ***
Filter   9752    9752     1 222.98  0.7341    0.3925    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
calc_contrasts(mResting)
         contrast estimate   SE  df t.ratio p.value lower.CL upper.CL
1    jar_vs_small       46 23.2 232    2.00   0.186      -17      109
2   jar_vs_medium       -4 26.3 239   -0.16  >0.999      -75       67
3    jar_vs_large      110 26.3 239    4.19  <0.001       39      181
4 small_vs_medium      -51 26.2 241   -1.93   0.186     -122       21
5  small_vs_large       64 26.2 241    2.43   0.080       -8      135
6 medium_vs_large      114 22.6 231    5.05  <0.001       53      175
7 large_vs_barren       -5 23.2 231   -0.22  >0.999      -68       58
emmeans(mResting, ~ Time)
 Time     emmean   SE   df lower.CL upper.CL
 7:00 AM     141 29.1 19.6     80.1      202
 10:00 AM    199 29.1 19.6    137.8      259
 2:00 PM     259 29.1 19.6    198.1      320
 6:00 PM     286 29.1 19.6    225.7      347

Results are averaged over the levels of: Tank, Filter 
Degrees-of-freedom method: kenward-roger 
Confidence level used: 0.95 

Effect of Tank and Time.

  • Tank. Large rested significantly less time than Jar and Medium (almost significant for Small). Fish in Large rested 110 sec [CI 39, 181] more than fish in Jar (over a 600 sec trial), and 114 sec [CI 53, 175] more fish in Medium. This is in line with the PCA and the results of Swimming: Fish that spent time swimming spent less time resting. Fish in Large were overall more active. No significant difference between Large and Barren.
  • Time. Fish rest less time in the morning, especially at 7 am. In line with the PCA and the results of Swimming.
# Uncomment the 2 lines below if running the script for the first time
## rpt.resting <- rpt(Resting ~ Tank + Time + Filter + (1|Fish), grname = "Fish", data = data, datatype = "Gaussian", nboot = 1000, npermut = 1000)
## save(rpt.resting, file = "Script/Repeatability rptR output/rpt.resting")
base::load("Script/Repeatability rptR output/rpt.resting")
summary(rpt.resting)

Repeatability estimation using the lmm method

Call = rpt(formula = Resting ~ Tank + Time + Filter + (1 | Fish), grname = "Fish", data = data, datatype = "Gaussian", nboot = 1000, npermut = 1000)

Data: 252 observations
----------------------------------------

Fish (13 groups)

Repeatability estimation overview: 
      R     SE   2.5%  97.5% P_permut  LRT_P
  0.369  0.106  0.153  0.562    0.001      0

Bootstrapping and Permutation test: 
            N    Mean Median   2.5%  97.5%
boot     1000 0.36078  0.359  0.153 0.5617
permut   1000 0.00933  0.000  0.000 0.0452

Likelihood ratio test: 
logLik full model = -1565.567
logLik red. model = -1599.854
D  = 68.6, df = 1, P = 6.1e-17

----------------------------------------

Likelihood ratio test indicates a statistically significant random effect. Meaning that fish behaved differently among each others in terms of swimming. The repeatability is OK 0.37 [95% CI 0.15, 0.56].

lm_diagnosis(mResting)
[1] "check constant variance over fitted values"

[1] "Normality of Resting"

[1] "Normality random residuals"

The model seems good.

Just a plot with the amount of time resting at the different places depending on the tank. Each bar value is the mean of resting times over trials and fish.

From the graphs, we can see that fish use different places to rest, not only one type. They use all the different places available. It does not seem that there is a preferred resting place.

hist1(data$Hovering)

table(data$Hovering==0)

FALSE  TRUE 
  204    48 
descdist(data$Hovering, boot = 1000, print = FALSE)

quantile(data$Hovering)
    0%    25%    50%    75%   100% 
  0.00   6.01  23.71  56.21 344.27 
hist1(data$Hovering[data$Hovering!=0]) # histogram without the zeros

Even if there are less zeros than Foraging or Stereotypic Swimming, it is not a normal distribution. There were only 48 trials (out of 252) during which no hovering happened. So 204 trials where we could study the amount of time hovering in 204 trials.

We fitted a glm distribution like Foraging and Stereotypic Swimming on the probability to hover (dependent variable is 1 if hovering occurred during the trial, zero otherwise).

To analyse the amount of time the fish was hovering on the 204 trials with hovering time > 0, we tried different models described in “Analysis_Hovering”. The best model was a linear mixed model on the amount of time spent hovering with weighted least squares.

# Effect of tank of the probability to hover during a trial
plot_var_binary(data, "Hovering.bin")

plot_var_binary_fish(data, "Hovering.bin")

# Among the trials where the fish hovers, effect of tank on the amount of time hovering
data %>% filter(Hovering >0) %>% 
  plot_var(., "Hovering")

data %>% filter(Hovering >0) %>% 
  plot_var_fish(., "Hovering")

Both the probability to hover and the time spent hovering seems to be higher in Jar compared to the others. All fish besides Goblin hovered more in Jar compared to Small.

Y variable = probability to hover during a trial

mHovering <- glmmTMB(Hovering.bin ~ Tank + Time + Filter + (1|Fish), data = data, family =binomial)

glmmTMB:::Anova.glmmTMB(mHovering)
Analysis of Deviance Table (Type II Wald chisquare tests)

Response: Hovering.bin
        Chisq Df Pr(>Chisq)  
Tank   6.8189  4    0.14578  
Time   9.3478  3    0.02501 *
Filter 4.3176  1    0.03772 *
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
emmeans(mHovering, ~ Time, type = "response")
 Time      prob     SE  df asymp.LCL asymp.UCL
 7:00 AM  0.917 0.0398 Inf     0.798     0.969
 10:00 AM 0.870 0.0525 Inf     0.729     0.943
 2:00 PM  0.728 0.0786 Inf     0.551     0.854
 6:00 PM  0.747 0.0760 Inf     0.573     0.866

Results are averaged over the levels of: Tank, Filter 
Confidence level used: 0.95 
Intervals are back-transformed from the logit scale 
emmeans(mHovering, ~ Filter, type = "response")
 Filter     prob     SE  df asymp.LCL asymp.UCL
 No-filter 0.900 0.0343 Inf     0.810     0.950
 Filter    0.729 0.0933 Inf     0.516     0.872

Results are averaged over the levels of: Tank, Time 
Confidence level used: 0.95 
Intervals are back-transformed from the logit scale 

No effect of Tank on the probability to hover during a trial, only an effect of Time and filter

  • Time. Fish were less likely to hover in the afternoon.
  • Filter. Fish were less likely to hover with a filter in their tank.
mHovering_res <- simulateResiduals(mHovering)
plot(mHovering_res)

The model seems good.

Y variable = amount of time spent hovering during a trial. Only trials with hovering time > 0

Linear model with weighted least squares

mHovering2 <- lme(Hovering ~ Tank + Time + Filter, random = ~1 | Fish, data = data[data$Hovering>0,], weights = varPower(), control = lmeControl(maxIter = 20000))

anova(mHovering2)
            numDF denDF  F-value p-value
(Intercept)     1   183 43.85489  <.0001
Tank            4   183 18.75147  <.0001
Time            3   183  3.66785  0.0134
Filter          1   183  5.29132  0.0226
calc_contrasts(mHovering2)
         contrast estimate   SE  df t.ratio p.value lower.CL upper.CL
1    jar_vs_small       53 12.0 183    4.40  <0.001       20       85
2   jar_vs_medium       53 12.0 183    4.42  <0.001       20       86
3    jar_vs_large       61 11.9 183    5.10  <0.001       28       93
4 small_vs_medium        1  6.5 183    0.08   0.938      -17       18
5  small_vs_large        8  6.2 183    1.30   0.394       -9       25
6 medium_vs_large        8  2.7 183    2.81   0.016        0       15
7 large_vs_barren      -22  4.9 183   -4.53  <0.001      -35       -9
emmeans(mHovering2, ~ Time) # estimated means of time spent Hovering depending on the time of the day 
 Time     emmean   SE df lower.CL upper.CL
 7:00 AM    39.3 5.06 12     28.2     50.3
 10:00 AM   48.3 5.72 12     35.8     60.8
 2:00 PM    48.1 5.68 12     35.7     60.4
 6:00 PM    42.4 5.23 12     31.0     53.8

Results are averaged over the levels of: Tank, Filter 
Degrees-of-freedom method: containment 
Confidence level used: 0.95 
emmeans(mHovering2, ~ Filter)
 Filter    emmean   SE df lower.CL upper.CL
 No-filter   52.2 5.30 12     40.7     63.8
 Filter      36.8 6.53 12     22.5     51.0

Results are averaged over the levels of: Tank, Time 
Degrees-of-freedom method: containment 
Confidence level used: 0.95 

Effect of Tank, Time and Filter.

  • Tank. Jar hovered significantly more time than Small, Medium and Large. Fish in Jar hovered 53 sec [CI 20, 85] more than Small (over a 600 sec trial), 53 sec [CI 20, 86] more than Medium, and 61 sec [CI 28, 93] more than Large. Fish in medium hovered more time than fish in Large. In addition, fish in Barren hovered significantly more time (22 sec [CI 9, 35]) than fish in Large.
  • Time. Fish hovered more time in the middle of the day.
  • Filter. Fish hovered less time with a filter in their tank.
plot(mHovering2, resid(., type = "pearson") ~ fitted(.), aspect = 1, pch = 21, abline = 0)

qqnorm(resid(mHovering2, type = "pearson"))
qqline(resid(mHovering2, type = "pearson"))

ranefPlot <- ranef(mHovering2, level = 1)
qqnorm(ranefPlot$`(Intercept)`)
qqline(ranefPlot$`(Intercept)`)

The model seems good besides the normality. But linear models are robust to the normality assumption (= we can still draw true inference).

Just a plot with where the fish hovers at the different places depending on the tank.

Fish used different places for hovering in all tanks.

hist1(data$Foraging)

table(data$Foraging==0)

FALSE  TRUE 
   84   168 
descdist(data$Foraging, boot = 1000, print = FALSE)

The data are clearly not following a normal distribution. It seems that a beta distribution could suit but foraging is not a continuous variable ranging from 0 to 1. Foraging has thus been analysed as a binary variable: 1 if fish spent time foraging during this trial, 0 otherwise.

There were 168 trials (out of 252) during which no foraging happened.

The plots are in percentage of trials during which the fish foraged over the total number of trials.

plot_var_binary(data, "Foraging.bin")

plot_var_binary_fish(data, "Foraging.bin")

mForaging <- glmmTMB(Foraging.bin ~ Tank + Time + Filter + (1|Fish), data = data, family =binomial)

glmmTMB:::Anova.glmmTMB(mForaging)
Analysis of Deviance Table (Type II Wald chisquare tests)

Response: Foraging.bin
         Chisq Df Pr(>Chisq)    
Tank   32.8992  4  1.253e-06 ***
Time    1.8103  3     0.6127    
Filter  0.8031  1     0.3702    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
calc_contrasts_bin(mForaging)
         contrast odds.ratio   SE  df null z.ratio p.value asymp.LCL asymp.UCL
1    jar_vs_small       1.88 1.03 Inf    1    1.15   0.251     0.428     8.250
2   jar_vs_medium       0.29 0.17 Inf    1   -2.07   0.078     0.056     1.459
3    jar_vs_large       0.08 0.05 Inf    1   -3.97  <0.001     0.014     0.437
4 small_vs_medium       0.15 0.10 Inf    1   -2.90   0.015     0.027     0.875
5  small_vs_large       0.04 0.03 Inf    1   -4.63  <0.001     0.006     0.262
6 medium_vs_large       0.27 0.13 Inf    1   -2.79   0.016     0.076     0.952
7 large_vs_barren      14.64 7.98 Inf    1    4.92  <0.001     3.378    63.486
# example of differences between Jar and Large
## Contrast estimate
1/0.08
[1] 12.5
## Lower CI
1/0.437
[1] 2.28833
## Upper CI
1/0.014
[1] 71.42857

Effect of Tank.

As a reminder, an odds.ratio < 1 between tank X vs tank Y means the probability of foraging during a trial in tank X is smaller that the probability of foraging during a trial in tank Y. An odd ratio for instance of 0.2 means that a fish is (1 / 0.2) = five times more likely to forage during a trial in tank Y than in Tank X. If we imagine 5 trials, fish will forage on average during 1 trial out of 5 in Tank X whereas fish will forage on average during 5 trials out of 5 in Tank Y.

  • Tank. Fish were more likely to forage during a trial in Large compared to Jar, Small and Medium. Again in accordance with the PCA and the results of Swimming and Resting. Fish in Large were on average 13 times [CI 2, 71] more likely to forage during a trial than fish in Jar, 25 times [CI 4, 166] than fish in Small, and 4 times [CI 1, 13] than fish in Medium. In addition, fish were 15 times [CI 3, 63] more likely to forage during a trial in Large than Barren. In accordance with the PCA and the results of Swimming. This time, there was also a significant difference between small and medium.
# Attempt to calculate repeatability
rpt.foraging <- rpt(Foraging.bin ~ Tank + Time + Filter + (1|Fish), grname = "Fish", data = data, datatype = "Binary", nboot = 0, npermut = 0)
Warning in checkConv(attr(opt, "derivs"), opt$par, ctrl = control$checkConv, :
Model failed to converge with max|grad| = 0.00341065 (tol = 0.002, component 1)
Warning in checkConv(attr(opt, "derivs"), opt$par, ctrl = control$checkConv, :
Model failed to converge with max|grad| = 0.00341065 (tol = 0.002, component 1)
# attempt with package glmer rather than glmmTMB
mForaging2 <- glmer(Foraging.bin ~ Tank + Time + Filter + (1|Fish), data = data, family = "binomial")
Warning in checkConv(attr(opt, "derivs"), opt$par, ctrl = control$checkConv, :
Model failed to converge with max|grad| = 0.00341065 (tol = 0.002, component 1)
# Likelihood ratio test of random effect
mForaging0 <- glmmTMB(Foraging.bin ~ Tank + Time + Filter, data = data, family =binomial)
anova(mForaging0, mForaging)
Data: data
Models:
mForaging0: Foraging.bin ~ Tank + Time + Filter, zi=~0, disp=~1
mForaging: Foraging.bin ~ Tank + Time + Filter + (1 | Fish), zi=~0, disp=~1
           Df    AIC    BIC  logLik deviance  Chisq Chi Df Pr(>Chisq)    
mForaging0  9 297.42 329.18 -139.71   279.42                             
mForaging  10 280.69 315.98 -130.34   260.69 18.734      1  1.503e-05 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Error when calculating the repeatability with rptR. Hard to estimate repeatability on binary data. I don’t know how to do it without the package rptR. I think we don’t have enough data power anyway to calculate repeatability with a binary variable. I tried to use the lmer package to calculate the repeatability with rptR but the model failed to converge when using glmer().

Likelihood ratio test indicates a statistically significant random effect. Meaning that fish behaved differently among each others in terms of foraging.

mForaging_res <- simulateResiduals(mForaging)
plot(mForaging_res)

plotResiduals(mForaging_res, form = data$Tank)

plotResiduals(mForaging_res, form = data$Time)

plotResiduals(mForaging_res, form = data$Filter)

The model seems good.

hist1(data$Stereotypic.swimming)

table(data$Stereotypic.swimming==0)

FALSE  TRUE 
   80   172 
descdist(data$Stereotypic.swimming, boot = 1000, print = FALSE)

Idem Foraging. Analysis as binary variable if the fish performs stereotypic swimming during a trial or not.

There were 172 trials (out of 252) during which no stereotypic swimming happened.

(plotSS1 <- plot_var_binary(data, "SS.bin"))

(plotSS2 <- plot_var_binary_fish(data, "SS.bin"))

Huge variability between fish as always. Some fish never performed stereotypic swimming while some in all tanks.

mSS <- glmmTMB(SS.bin ~ Tank + Time + Filter + (1|Fish), data = data, family =binomial)

glmmTMB:::Anova.glmmTMB(mSS)
Analysis of Deviance Table (Type II Wald chisquare tests)

Response: SS.bin
         Chisq Df Pr(>Chisq)    
Tank   24.6257  4  5.982e-05 ***
Time    3.0947  3     0.3773    
Filter  1.1698  1     0.2795    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
calc_contrasts_bin(mSS)
         contrast odds.ratio   SE  df null z.ratio p.value asymp.LCL asymp.UCL
1    jar_vs_small       0.03 0.02 Inf    1   -4.56  <0.001     0.003     0.229
2   jar_vs_medium       0.22 0.21 Inf    1   -1.59   0.284     0.016     2.880
3    jar_vs_large       0.18 0.18 Inf    1   -1.77   0.284     0.014     2.417
4 small_vs_medium       7.87 6.78 Inf    1    2.39   0.100     0.775    79.810
5  small_vs_large       6.60 5.65 Inf    1    2.20   0.138     0.660    66.078
6 medium_vs_large       0.84 0.50 Inf    1   -0.30   0.767     0.171     4.130
7 large_vs_barren       0.32 0.20 Inf    1   -1.80   0.284     0.060     1.743

Effect of Tank.

  • Tank. Jar were less likely to perform stereotypic swimming during a trial than Small/Medium/Large but only significant between Jar and Small. Fish in Small were 33 times [CI 4, 333] more likely to perform stereotypic swimming during a trial than fish in Jar.
# Attempt to calculate repeatability
rpt.SS <- rpt(SS.bin ~ Tank + Time + Filter + (1|Fish), grname = "Fish", data = data, datatype = "Binary", nboot = 0, npermut = 0)
Warning in checkConv(attr(opt, "derivs"), opt$par, ctrl = control$checkConv, :
Model failed to converge with max|grad| = 0.00627069 (tol = 0.002, component 1)
Warning in checkConv(attr(opt, "derivs"), opt$par, ctrl = control$checkConv, :
Model failed to converge with max|grad| = 0.00627069 (tol = 0.002, component 1)
# attempt with package glmer rather than glmmTMB
mSS2 <- glmer(SS.bin ~ Tank + Time + Filter + (1|Fish), data = data, family = "binomial")
Warning in checkConv(attr(opt, "derivs"), opt$par, ctrl = control$checkConv, :
Model failed to converge with max|grad| = 0.00627069 (tol = 0.002, component 1)
# Likelihood ratio test of random effect
mSS0 <- glmmTMB(SS.bin ~ Tank + Time + Filter, data = data, family =binomial)
anova(mSS0, mSS)
Data: data
Models:
mSS0: SS.bin ~ Tank + Time + Filter, zi=~0, disp=~1
mSS: SS.bin ~ Tank + Time + Filter + (1 | Fish), zi=~0, disp=~1
     Df    AIC    BIC  logLik deviance  Chisq Chi Df Pr(>Chisq)    
mSS0  9 277.42 309.19 -129.71   259.42                             
mSS  10 205.76 241.05  -92.88   185.76 73.661      1  < 2.2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Error when calculating the repeatability with rptR, idem Foraging.

Likelihood ratio test indicates a statistically significant random effect. Meaning that fish behaved differently among each others in terms of stereotypic swimming.

mSS_res <- simulateResiduals(mSS)
plot(mSS_res)

plotResiduals(mSS_res, form = data$Tank)

plotResiduals(mSS_res, form = data$Time)

plotResiduals(mSS_res, form = data$Filter)

# Problem with Filter, I retried the model without Filter
mSS2 <- glmmTMB(SS.bin ~ Tank + Time + (1|Fish), data = data, family =binomial)
plot(simulateResiduals(mSS2))

It is expected to have a problem with filter as there are more than twice more trials without filter than with filter. No problem when removing Filter from the model. The model seems good.

Just a plot with the type of stereotypic swimming depending on the tank. To stick with the analysis, it is a count (count = 1 if the fish performs a certain type of stereotypic swimming during a trial).

It seems that there are not differences between tanks. Just circles seem specific to larger tanks (Medium/Large/Barren).

     Tank          Fish     Time Stereotypic.swimming.type Nb.rep
1   Small        Dragon  7:00 AM                    Pacing    328
2   Small        Dragon 10:00 AM                    Pacing     76
3   Small        Dragon  2:00 PM                    Pacing     20
4   Small        Dragon  6:00 PM                    Pacing      8
5   Small        Dragon  6:00 PM         Pacing front wall      8
6  Barren        Dragon  7:00 AM                    Pacing      4
7  Barren        Dragon 10:00 AM                    Pacing      5
8  Barren        Dragon  2:00 PM                    Pacing     19
9  Barren        Dragon  6:00 PM                    Pacing      4
10    Jar           Elf  7:00 AM                   Zig zag      4
11    Jar           Elf  6:00 PM                   Zig zag      6
12  Small           Elf  7:00 AM                    Pacing     15
13  Small           Elf 10:00 AM                    Pacing     15
14  Small           Elf  2:00 PM                    Pacing    109
15  Small           Elf  6:00 PM                    Pacing     25
16 Medium           Elf  7:00 AM                    Pacing      4
17 Medium           Elf 10:00 AM                    Pacing     34
18 Medium           Elf  2:00 PM                    Pacing     14
19  Large           Elf 10:00 AM                   Circles     12
20  Large           Elf 10:00 AM                    Pacing      8
21 Barren           Elf  7:00 AM                    Pacing    186
22 Barren           Elf 10:00 AM                    Pacing      8
23 Barren           Elf  6:00 PM                    Pacing      8
24  Small         Fairy  7:00 AM                    Pacing    234
25  Small         Fairy 10:00 AM                    Pacing    340
26  Small         Fairy  2:00 PM                    Pacing    381
27  Small         Fairy  6:00 PM                    Pacing    212
28  Large         Fairy 10:00 AM                   Circles      8
29  Large         Fairy 10:00 AM                    Pacing     22
30  Large         Fairy  2:00 PM                    Pacing      7
31 Barren         Fairy  7:00 AM                    Pacing      6
32 Barren         Fairy 10:00 AM                    Pacing    396
33 Barren         Fairy  2:00 PM                    Pacing    132
34 Barren         Fairy  6:00 PM                    Pacing    111
35  Small         Ghost  7:00 AM                    Pacing      3
36  Small        Kraken  7:00 AM                    Pacing      6
37  Small        Kraken  7:00 AM       Pacing with a hover    790
38  Small        Kraken 10:00 AM       Pacing with a hover   1204
39  Small        Kraken 10:00 AM                   Zig zag     77
40  Small        Kraken  2:00 PM       Pacing with a hover    746
41  Small        Kraken  2:00 PM                   Zig zag     64
42 Medium        Kraken 10:00 AM                   Circles     13
43 Medium        Kraken  2:00 PM                   Circles     22
44  Large        Kraken  7:00 AM                   Circles      9
45  Large        Kraken 10:00 AM                    Pacing     77
46  Large        Kraken  2:00 PM                   Circles     43
47  Large        Kraken  2:00 PM                    Pacing     41
48  Large        Kraken  6:00 PM                   Circles      6
49 Barren        Kraken  7:00 AM                   Circles     15
50 Barren        Kraken  7:00 AM                   Zig zag     11
51 Barren        Kraken 10:00 AM                   Circles     41
52 Barren        Kraken 10:00 AM                    Pacing     28
53 Barren        Kraken  2:00 PM                    Pacing     21
54 Barren        Kraken  2:00 PM                   Zig zag     12
55 Barren        Kraken  6:00 PM                   Circles      7
56 Barren        Kraken  6:00 PM                    Pacing      0
57  Small Orange Pendek 10:00 AM                    Pacing      5
58 Medium Orange Pendek  7:00 AM                    Pacing      8
59 Medium Orange Pendek 10:00 AM                    Pacing     41
60 Medium Orange Pendek  2:00 PM                    Pacing      4
61 Medium Orange Pendek  6:00 PM                    Pacing      3
62  Large Orange Pendek  7:00 AM                   Circles     15
63    Jar       Phoenix 10:00 AM                    Pacing      3
64  Small       Phoenix  7:00 AM                    Pacing      7
65  Small       Phoenix  2:00 PM                    Pacing     14
66  Small       Phoenix  6:00 PM                    Pacing      8
67 Medium       Phoenix  7:00 AM                    Pacing     36
68 Medium       Phoenix 10:00 AM                    Pacing     19
69 Medium       Phoenix  2:00 PM                    Pacing    100
70 Medium       Phoenix  6:00 PM                    Pacing     58
71  Large       Phoenix  7:00 AM                    Pacing      6
72  Large       Phoenix 10:00 AM                    Pacing     19
73  Large       Phoenix  2:00 PM                    Pacing     13
74  Large       Phoenix  6:00 PM                    Pacing      8
75 Barren       Phoenix 10:00 AM                   Circles     27
76 Barren       Phoenix 10:00 AM                    Pacing     18
77 Barren       Phoenix  2:00 PM           Pace then hover      0
78 Barren       Phoenix  6:00 PM                   Circles      3
79 Barren       Phoenix  6:00 PM           Pace then hover      0
80    Jar        Wizard  2:00 PM                    Pacing      7
81  Small        Wizard  7:00 AM                    Pacing    681
82  Small        Wizard 10:00 AM                    Pacing      8
83  Small        Wizard  6:00 PM                    Pacing     52
84 Medium        Wizard  7:00 AM                    Pacing     35
85 Medium        Wizard 10:00 AM                    Pacing     16
86  Large        Wizard  7:00 AM                    Pacing     35
87  Large        Wizard 10:00 AM                    Pacing    409
88  Large        Wizard  2:00 PM                    Pacing    277
89  Large        Wizard  6:00 PM                    Pacing    185
90 Barren        Wizard  7:00 AM                    Pacing    527
91 Barren        Wizard 10:00 AM                    Pacing    472
92 Barren        Wizard  2:00 PM                    Pacing    432
93 Barren        Wizard  6:00 PM                    Pacing    641

I don’t think there are enough data to really say something about the number of repetitions. The number of repetitions seems pretty unique to a fish in a certain tank. I don’t see an obvious pattern depending on the tank. The interesting thing is that the stereotypic swimming seemed consistent in a tank for a fish (most or all trials of that day with stereotypic swimming). It is a pitty we couldn’t calculate the repeatability of the binomial glm of Stereotypic Swimming because it would have been interesting.

hist1(data$Interation.with.surface)

table(data$Interation.with.surface==0)

FALSE  TRUE 
  127   125 

Idem analysis binary generalised linear models.

(plotInteracting1 <- plot_var_binary(data, "Interacting.bin"))

(plotInteracting2 <- plot_var_binary_fish(data, "Interacting.bin"))

mInteracting <- glmmTMB(Interacting.bin ~ Tank + Time + Filter + (1|Fish), data = data, family =binomial)

glmmTMB:::Anova.glmmTMB(mInteracting)
Analysis of Deviance Table (Type II Wald chisquare tests)

Response: Interacting.bin
         Chisq Df Pr(>Chisq)   
Tank   17.1723  4   0.001789 **
Time    5.0268  3   0.169846   
Filter  0.0876  1   0.767253   
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
calc_contrasts_bin(mInteracting)
         contrast odds.ratio   SE  df null z.ratio p.value asymp.LCL asymp.UCL
1    jar_vs_small       1.68 0.75 Inf    1    1.15   0.497     0.503     5.580
2   jar_vs_medium       0.69 0.35 Inf    1   -0.72   0.497     0.174     2.748
3    jar_vs_large       4.39 2.32 Inf    1    2.80   0.030     1.061    18.167
4 small_vs_medium       0.41 0.21 Inf    1   -1.74   0.251     0.105     1.625
5  small_vs_large       2.62 1.36 Inf    1    1.86   0.251     0.651    10.547
6 medium_vs_large       6.35 3.00 Inf    1    3.92  <0.001     1.783    22.628
7 large_vs_barren       0.30 0.14 Inf    1   -2.66   0.039     0.086     1.012

Effect of Tank.

  • Tank. Fish were more likely to interact with the surface during a trial in Jar/Medium compared to Large (not significant for Small). Fish in Jar were 4 times [CI 1, 18] more likely to interact with the surface during a trial than fish in Large, and Fish in Medium were 6 times [CI 2, 23] then fish in Large. In addition, fish were 3 times [CI 1, 12] more likely to interact with a surface during a trial in Barren than in Large.
mInteracting0 <- glmmTMB(Interacting.bin ~ Tank + Time + Filter, data = data, family =binomial)
anova(mInteracting0, mInteracting)
Data: data
Models:
mInteracting0: Interacting.bin ~ Tank + Time + Filter, zi=~0, disp=~1
mInteracting: Interacting.bin ~ Tank + Time + Filter + (1 | Fish), zi=~0, disp=~1
              Df    AIC    BIC logLik deviance  Chisq Chi Df Pr(>Chisq)    
mInteracting0  9 346.41 378.17 -164.2   328.41                             
mInteracting  10 329.40 364.69 -154.7   309.40 19.008      1  1.302e-05 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Likelihood ratio test indicates a statistically significant random effect. Meaning that fish behaved differently among each others in terms of interaction with surface.

mInteracting_res <- simulateResiduals(mInteracting)
plot(mInteracting_res)

plotResiduals(mInteracting_res, form = data$Tank)

The model seems good.

Just a plot with the type of interaction depending on the tank. To stick with the analysis, it is a count (count = 1 if the fish performs a certain type of interaction with surface during a trial).

The second plot groups similar interactions with surface together.

hist1(data$Nest.building)

table(data$Nest.building==0)

FALSE  TRUE 
   51   201 

There are 1 trial out of 5 where there was nest building. Analysis with binary generalised model.

(plotNest1 <- plot_var_binary(data, "Nest.bin"))

(plotNest2<- plot_var_binary_fish(data, "Nest.bin"))

mNest <- glmmTMB(Nest.bin ~ Tank + Time + Filter + (1|Fish), data = data, family =binomial)

glmmTMB:::Anova.glmmTMB(mNest)
Analysis of Deviance Table (Type II Wald chisquare tests)

Response: Nest.bin
         Chisq Df Pr(>Chisq)   
Tank   10.1022  4   0.038741 * 
Time    7.1655  3   0.066804 . 
Filter 10.3912  1   0.001266 **
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
calc_contrasts_bin(mNest)
         contrast odds.ratio   SE  df null z.ratio p.value asymp.LCL asymp.UCL
1    jar_vs_small       0.29 0.16 Inf    1   -2.24   0.150     0.065     1.283
2   jar_vs_medium       0.25 0.16 Inf    1   -2.15   0.158     0.046     1.410
3    jar_vs_large       0.22 0.14 Inf    1   -2.40   0.116     0.040     1.205
4 small_vs_medium       0.89 0.50 Inf    1   -0.22  >0.999     0.196     3.992
5  small_vs_large       0.76 0.42 Inf    1   -0.49  >0.999     0.171     3.398
6 medium_vs_large       0.86 0.47 Inf    1   -0.27  >0.999     0.196     3.770
7 large_vs_barren       4.17 2.90 Inf    1    2.05   0.162     0.639    27.143
emmeans(mNest, ~ Filter, type = "response")
 Filter      prob     SE  df asymp.LCL asymp.UCL
 No-filter 0.2081 0.0621 Inf   0.11154    0.3549
 Filter    0.0234 0.0170 Inf   0.00552    0.0935

Results are averaged over the levels of: Tank, Time 
Confidence level used: 0.95 
Intervals are back-transformed from the logit scale 

Effect of Tank and Filter. + Tank. Despite a significant effect of Tank, no contrasts were statistically significant. This is usual when you have a weakly significant fixed effect of a factor (0.01 < p-value < 0.05) which has a lot of different levels. In terms of trend, fish in Jar seemed less likely to build a nest compared to Small/Medium/Large. In addition, fish in Barren were less likely to build nest than fish in Large. + Filter. Fish were more likely to build a nest without a filter in their tank.

mNest0 <- glmmTMB(Nest.bin ~ Tank + Time + Filter, data = data, family =binomial)
anova(mNest0, mNest)
Data: data
Models:
mNest0: Nest.bin ~ Tank + Time + Filter, zi=~0, disp=~1
mNest: Nest.bin ~ Tank + Time + Filter + (1 | Fish), zi=~0, disp=~1
       Df    AIC    BIC  logLik deviance  Chisq Chi Df Pr(>Chisq)    
mNest0  9 238.24 270.00 -110.12   220.24                             
mNest  10 228.68 263.98 -104.34   208.68 11.552      1  0.0006769 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Likelihood ratio test indicates a statistically significant random effect. Meaning that fish behaved differently among each others in terms of nest building.

mNest_res <- simulateResiduals(mNest)
plot(mNest_res)

plotResiduals(mNest_res, form = data$Tank)

plotResiduals(mNest_res, form = data$Time)

plotResiduals(mNest_res, form = data$Filter)

The model seems good.

ggplot(dataUpDown, aes(y = adj.up, x = Tank)) +
    geom_jitter(alpha = .5, width = 0.05, height=0, color = grey(0.25)) +
    stat_summary(fun.y = mean,geom = "point",colour = "red", size = 4)+
    stat_summary(fun.min = function(x) mean(x) - sd(x), 
                 fun.max = function(x) mean(x) + sd(x), 
                 geom = "errorbar",colour = "red", width = 0.15)+
  ylab("Time up (sec)")

It seems that the tank has an influence on whether fish prefer being on the upper half or lower half of the tank.

We can do different models: + Look at the percentage of time spent up of the trial. Fit a beta distribution. Problem: All percentages need to be 0 < perc < 100%. There are trials with 100% so we can’t use beta distribution.

  • Look at the count of seconds (how many seconds the fish spend up of the tank). Fit a binomial distribution. Problem: The diagnostics plots were very bad when fitting mUp <- glmmTMB(cbind(up, down) ~ Tank + Time + Filter + (1|Fish), data = dataUpDown, family = binomial)

  • Look simply at the actual time spent up. Fit a normal distribution. The response variable can be adjusted by the fact that the trial were not perfectly 600 sec. \(adj.up= \frac{\text{time spent up} * 600}{\text{trial total duration}}\). It is what has been done.

mUp <- lmer(adj.up ~ Tank + Time + Filter + (1|Fish), data = dataUpDown)
anova(mUp, ddf = "Kenward-Roger", type = 2)
Type II Analysis of Variance Table with Kenward-Roger's method
       Sum Sq Mean Sq NumDF  DenDF F value    Pr(>F)    
Tank   259014   64753     4 237.13  3.9094 0.0042910 ** 
Time   284189   94730     3 231.06  5.7200 0.0008588 ***
Filter  74698   74698     1 135.88  4.5105 0.0354999 *  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
calc_contrasts(mUp)
         contrast estimate   SE  df t.ratio p.value lower.CL upper.CL
1    jar_vs_small      -70 25.9 232   -2.72   0.042     -141       -0
2   jar_vs_medium       33 29.0 243    1.14   0.769      -46      112
3    jar_vs_large       -3 29.0 243   -0.09   0.926      -81       76
4 small_vs_medium      103 28.8 243    3.59   0.003       25      182
5  small_vs_large       68 28.8 243    2.35   0.097      -10      146
6 medium_vs_large      -36 25.2 231   -1.41   0.635     -104       33
7 large_vs_barren      -20 25.9 232   -0.78   0.874      -90       50
emmeans(mUp, ~ Time) # estimated means of time spent swimming depending on the time of the day 
 Time     emmean   SE   df lower.CL upper.CL
 7:00 AM     275 23.9 31.5      227      324
 10:00 AM    281 23.9 31.5      232      329
 2:00 PM     225 23.9 31.5      177      274
 6:00 PM     201 23.9 31.5      152      250

Results are averaged over the levels of: Tank, Filter 
Degrees-of-freedom method: kenward-roger 
Confidence level used: 0.95 
emmeans(mUp, ~ Filter) # estimated means of time spent swimming depending on the time of the day
 Filter    emmean   SE   df lower.CL upper.CL
 No-filter    278 20.2 15.4      235      321
 Filter       214 28.2 35.1      156      271

Results are averaged over the levels of: Tank, Time 
Degrees-of-freedom method: kenward-roger 
Confidence level used: 0.95 

Effect of Tank, Time and Filter.

  • Tank. The only statistically significant difference is in between Small and Medium: Fish in Small tank spent on average 103 more seconds [25, 181 95% CI] on the upper half of the tank compared to fish in Medium tanks.
  • Time. Fish spent more time on the upper half of the tank in the morning.
  • Filter. Fish spent more time on the upper half of the tank with a filter in their tank.
lm_diagnosis(mUp)
[1] "check constant variance over fitted values"

[1] "Normality of adj.up"

[1] "Normality random residuals"

The model seems good.

What we can say first is that there is a effect of the Tank on all behaviours, the tank is there having a big influence on the betta’s behaviour. Then, it seems that we have three groups for the Jar/Small/Medium/Large

  • Jar with less likely to build a nest and less likely to perform stereotypic swimming compared to the others
  • Small and Medium
  • Large with more time swimming, less time resting, more likely to forage and less likely to interact with the surface

And Large and Barren are two different groups + Barren with less likely to build a nest + Large with more time swimming, more likely to forage and less likely to interact with the surface

Fianlly, we can say that each fish behave pretty uniquely.

sessionInfo()
R version 4.3.1 (2023-06-16 ucrt)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 19045)

Matrix products: default


locale:
[1] LC_COLLATE=English_United States.utf8 
[2] LC_CTYPE=English_United States.utf8   
[3] LC_MONETARY=English_United States.utf8
[4] LC_NUMERIC=C                          
[5] LC_TIME=English_United States.utf8    

time zone: Australia/Sydney
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices datasets  utils     methods   base     

other attached packages:
 [1] renv_1.0.7          nlme_3.1-162        performance_0.11.0 
 [4] rptR_0.9.22         DHARMa_0.4.6        glmmTMB_1.1.9      
 [7] fitdistrplus_1.1-11 survival_3.5-5      MASS_7.3-60        
[10] emmeans_1.10.1      lmerTest_3.1-3      lme4_1.1-35.2      
[13] Matrix_1.6-5        FactoMineR_2.10     RColorBrewer_1.1-3 
[16] ggforce_0.4.2       ggrepel_0.9.5       ggpubr_0.6.0       
[19] lubridate_1.9.3     forcats_1.0.0       stringr_1.5.1      
[22] dplyr_1.1.3         purrr_1.0.2         readr_2.1.5        
[25] tidyr_1.3.1         tibble_3.2.1        ggplot2_3.5.0      
[28] tidyverse_2.0.0    

loaded via a namespace (and not attached):
 [1] Rdpack_2.6           rlang_1.1.1          magrittr_2.0.3      
 [4] compiler_4.3.1       mgcv_1.8-42          vctrs_0.6.3         
 [7] pkgconfig_2.0.3      fastmap_1.1.1        backports_1.4.1     
[10] labeling_0.4.3       utf8_1.2.3           promises_1.3.0      
[13] rmarkdown_2.26       tzdb_0.4.0           nloptr_2.0.3        
[16] xfun_0.43            jsonlite_1.8.8       flashClust_1.01-2   
[19] later_1.3.2          tweenr_2.0.3         broom_1.0.5         
[22] parallel_4.3.1       cluster_2.1.4        R6_2.5.1            
[25] gap.datasets_0.0.6   qgam_1.3.4           stringi_1.8.3       
[28] car_3.1-2            boot_1.3-28.1        numDeriv_2016.8-1.1 
[31] estimability_1.5     iterators_1.0.14     Rcpp_1.0.11         
[34] knitr_1.46           httpuv_1.6.15        splines_4.3.1       
[37] timechange_0.3.0     tidyselect_1.2.1     rstudioapi_0.16.0   
[40] abind_1.4-5          yaml_2.3.8           codetools_0.2-19    
[43] doParallel_1.0.17    TMB_1.9.11           plyr_1.8.9          
[46] lattice_0.21-8       shiny_1.8.1.1        withr_3.0.0         
[49] coda_0.19-4.1        evaluate_0.23        polyclip_1.10-6     
[52] pillar_1.9.0         gap_1.5-3            carData_3.0-5       
[55] foreach_1.5.2        DT_0.33              insight_0.19.10     
[58] generics_0.1.3       hms_1.1.3            munsell_0.5.1       
[61] scales_1.3.0         minqa_1.2.6          xtable_1.8-4        
[64] leaps_3.1            glue_1.6.2           scatterplot3d_0.3-44
[67] tools_4.3.1          see_0.8.3            ggsignif_0.6.4      
[70] mvtnorm_1.2-3        grid_4.3.1           rbibutils_2.2.16    
[73] colorspace_2.1-0     patchwork_1.2.0      cli_3.6.1           
[76] fansi_1.0.4          gtable_0.3.4         rstatix_0.7.2       
[79] digest_0.6.35        pbkrtest_0.5.2       htmlwidgets_1.6.4   
[82] farver_2.1.1         htmltools_0.5.8.1    lifecycle_1.0.4     
[85] multcompView_0.1-10  mime_0.12